package org.acooke.uykfd.walk

import java.io.File
import java.sql.Connection

import org.acooke.uykfd.db.ArtistRow
import org.acooke.uykfd.db.Properties._
import org.acooke.uykfd.db.Schema
import org.acooke.uykfd.db.TrackRow

import scala.collection.mutable.HashMap


abstract class BaseWalk(start: ArtistRow, nArtists: Int, nTracks: Int, path: String, 
                        badArtist:Double, badTrack: Double, dExponent: Double, cnxn: Connection) {

  val artists = new History[ArtistRow](nArtists)
  val tracks = new TrackHistoryForArtists(nTracks, cnxn)
  val maxDistance = Schema.LFM_ARTIST_DISTANCES.orm.maxWeight(cnxn)

//  println("\"" + start.value + "\"")
  private var artist = start
  artists.add(artist)

  def next = {
    artist = chooseArtist(artist)
    artists.add(artist)
    val track = chooseTrack(artist)
    tracks.add(track)
    track
  }

  protected def chooseArtist(previous: ArtistRow) = {
    val candidates = artistCandidates(previous)
    artists.avoid(candidates, badArtist)
    for (candidate <- candidates) candidates.scale(candidate, 1.0 / fanIn(candidate))
    tracks.weightArtists(candidates)
    if (artists.length > 0) measureArtists(candidates, artists.oldest)
    candidates.choose
  }

  protected def fanIn(candidate: ArtistRow) = 1.0

  protected def artistCandidates(previous: ArtistRow): Candidates[ArtistRow]

  protected def measureArtists(candidates: Candidates[ArtistRow], anchor: ArtistRow) = {
//    println("**********" + anchor)
    maxDistance match {
      case Some(maxD) =>
        for (candidate <- candidates) {
          val d = Schema.LFM_ARTIST_DISTANCES.orm.lookupNodes(candidate, anchor, cnxn) match {
            case Some(edge) => edge.weight
            case None => maxD
          }
          val score = Math.pow((d + 1.0) / (maxD + 1.0), dExponent)
//          println(d + " " + score + " " + maxD + " " + candidate.value)
          candidates.scale(candidate, score)
        }
      case None => // ignore
    }
  }

  protected def chooseTrack(artist: ArtistRow) = {
    val candidates = new Candidates[TrackRow]
    for (track <- Schema.TRACKS.orm.fromArtist(artist, cnxn))
      candidates.add(track, 1000.0)
    tracks.avoid(candidates, badTrack)
    candidates.choose
  }

  def print(n: Int) =
    for (_ <- 0.to(n)) System.err.println(path + next.path)
  
}  


class ArtistWalk(start: ArtistRow, nArtists: Int, nTracks: Int, dExponent: Double, penalty: Double,
                 path: String, cnxn: Connection)
  extends BaseWalk(start, nArtists, nTracks, path, 0.9, 0.1, dExponent, cnxn) {

  override protected def artistCandidates(previous: ArtistRow) = {
    val candidates = new Candidates[ArtistRow]
    for (edge <- Schema.LFM_ARTIST_CONNECTIONS.orm.allEdges(previous, cnxn))
      candidates.add(edge.other(previous, cnxn), edge.weight)
    candidates
  }

  override protected def fanIn(candidate: ArtistRow) =
    Schema.LFM_ARTIST_CONNECTIONS.orm.degree(candidate, cnxn) + 1

}  


class BacklinkWalk(start: ArtistRow, nArtists: Int, nTracks: Int, dExponent: Double, penalty: Double, 
                   path: String, cnxn: Connection)
  extends BaseWalk(start, nArtists, nTracks, path, 0.9, 0.1, dExponent, cnxn) {

  override protected def artistCandidates(previous: ArtistRow) = {
    val candidates = new Candidates[ArtistRow]
    for (edge <- Schema.LFM_ARTIST_BACKLINKS.orm.allEdges(previous, cnxn))
      candidates.add(edge.other(previous, cnxn), 1.0)
    candidates
  }

  override protected def fanIn(candidate: ArtistRow) =
    Schema.LFM_ARTIST_BACKLINKS.orm.inDegree(candidate, cnxn) + 1

}  

